<!doctype html>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
  <script src="../../../web-component-tester/browser.js"></script>
  <link rel="import" href="../../polymer.html">
  <link rel="import" href="styling-scoped-elements.html">
  <style>
    #priority[style-scope=x-styled], #priority.style-scope.x-styled {
      border: 1px solid black;
    }
  </style>
</head>
<body>

  <div class="scoped">no margin</div>

  <x-styled>
    <div class="content1">Foo</div>
    <div class="content2">Bar</div>
    <div class="content">Content</div>
  </x-styled>

  <x-styled class="wide"></x-styled>

  <button is="x-button"></button>
  <button class="special" is="x-button"></button>

  <x-dynamic-scope></x-dynamic-scope>

  <template id="bind" is="dom-bind">
    <div id="dom-bind-static" class="static">static</div>
    <span id="dom-bind-dynamic" class$="[[dynamic]]">[[dynamic]]</span>
  </template>

  <x-dynamic-svg></x-dynamic-svg>
  <x-specificity></x-specificity>
  <x-specificity class="bar"></x-specificity>
  <div is="x-specificity-parent">
    <div is="x-specificity-nested"></div>
  </div>
  <x-overriding></x-overriding>

  <x-scope-no-class>
    <div></div>
  </x-scope-no-class>

<script>
  suite('scoped-styling', function() {

  function assertComputed(element, value, property, pseudo) {
    var computed = getComputedStyle(element, pseudo);
    property = property || 'border-top-width';
    if (Array.isArray(value)) {
      assert.oneOf(computed[property], value, 'computed style incorrect for ' + property);
    } else {
      assert.equal(computed[property], value, 'computed style incorrect for ' + property);
    }
  }

  var styled = document.querySelector('x-styled');
  var styledWide = document.querySelector('x-styled.wide');
  var unscoped = document.querySelector('.scoped');
  var button = document.querySelector('[is=x-button]');
  var specialButton = document.querySelector('[is=x-button].special');

  test(':host, :host(...)', function() {
    assertComputed(styled, '1px');
    assertComputed(styledWide, '2px');
    assertComputed(styled, ['', 'none'], 'content', '::after');
    assertComputed(styledWide, ['"-content-"', '-content-'], 'content', '::after');
  });

  test(':host-context(...)', function() {
    assertComputed(styled.$.child.$.gchild1.$.target, '0px');
    assertComputed(styled.$.child.$.gchild2.$.target, '10px');
    assertComputed(styledWide.$.child.$.gchild1.$.target, '10px');
    assertComputed(styledWide.$.child.$.gchild2.$.target, '10px');
  });

  test('scoped selectors, simple and complex', function() {
    assertComputed(styled.$.simple, '3px');
    assertComputed(styled.$.complex1, '4px');
    assertComputed(styled.$.complex2, '4px');
  });

  test('media query scoped selectors', function() {
    assertComputed(styled.$.media, '5px');
  });

  test('upper bound encapsulation', function() {
    assertComputed(unscoped, '0px');
  });

  test('lower bound encapsulation', function() {
    assertComputed(styled.$.child.$.simple, '0px');
    assertComputed(styled.$.child.$.complex1, '0px');
    assertComputed(styled.$.child.$.complex2, '0px');
    assertComputed(styled.$.child.$.media, '0px');
  });

  test('::content selectors', function() {
    var content = document.querySelector('.content');
    var content1 = document.querySelector('.content1');
    var content2 = document.querySelector('.content2');
    assertComputed(content, '6px');
    assertComputed(content1, '13px');
    assertComputed(content2, '14px');
  });

  test('auto ::content selector', function() {
    var x = document.createElement('x-content');
    var d1 = document.createElement('div');
    d1.classList.add('auto-content');
    d1.textContent = 'auto-content';
    document.body.appendChild(x);
    Polymer.dom(x).appendChild(d1);
    Polymer.dom.flush();
    assertComputed(d1, '2px');
  });

  test('::content + descendant in complex selector', function() {
    var x = document.createElement('x-content');
    var d1 = document.createElement('div');
    d1.classList.add('complex-descendant');
    d1.textContent = 'complex-descendant';
    document.body.appendChild(x);
    Polymer.dom(x).appendChild(d1);
    Polymer.dom.flush();
    assertComputed(d1, '4px');
  });

  test('::content + descendant in complex selector does not leak', function() {
    var x = document.createElement('x-content');
    var d1 = document.createElement('div');
    d1.classList.add('complex-descendant');
    d1.textContent = 'complex-descendant';
    document.body.appendChild(x);
    document.body.appendChild(d1);
    assertComputed(d1, '0px');
  });

  test('::content + child in complex selector', function() {
    var x = document.createElement('x-content');
    var d1 = document.createElement('div');
    d1.classList.add('complex-child');
    d1.textContent = 'complex-child';
    document.body.appendChild(x);
    Polymer.dom(x).appendChild(d1);
    Polymer.dom.flush();
    assertComputed(d1, '6px');
  });

  test('auto ::slotted selector', function() {
    var x = document.createElement('x-slotted');
    var d1 = document.createElement('div');
    d1.classList.add('auto-content');
    d1.textContent = 'auto-content';
    document.body.appendChild(x);
    Polymer.dom(x).appendChild(d1);
    Polymer.dom.flush();
    assertComputed(d1, '2px');
  });

  test('::slotted + child in complex selector', function() {
    var x = document.createElement('x-slotted');
    var d1 = document.createElement('div');
    d1.classList.add('complex-child');
    d1.textContent = 'complex-child';
    document.body.appendChild(x);
    Polymer.dom(x).appendChild(d1);
    Polymer.dom.flush();
    assertComputed(d1, '6px');
  });

  test('::slotted + named slot', function() {
    var x = document.createElement('x-slotted');
    var d1 = document.createElement('div');
    d1.setAttribute('slot', 'container')
    d1.textContent = 'named slot child';
    document.body.appendChild(x);
    Polymer.dom(x).appendChild(d1);
    Polymer.dom.flush();
    assertComputed(d1, '8px');
  });

  test('::shadow selectors', function() {
    assertComputed(styled.$.child.$.shadow, '7px');
  });

  test('/deep/ selectors', function() {
    assertComputed(styled.$.child.$.deep, '8px');
  });

  test('elements dynamically added/removed from root', function() {
    var d = document.createElement('div');
    d.classList.add('scoped');
    d.textContent = 'Dynamically... Scoped!';
    Polymer.dom(styled.root).appendChild(d);
    Polymer.dom.flush();
    assertComputed(d, '4px');
    Polymer.dom(document.body).appendChild(d);
    Polymer.dom.flush();
    assert.notInclude(d.getAttribute('style-scoped') || '', styled.is, 'scoping attribute not removed when added to other root');
    assert.notInclude(d.className, styled.is, 'scoping class not removed when added to other root');
    Polymer.dom(styled.root).appendChild(d);
    Polymer.dom.flush();
    assertComputed(d, '4px');
    Polymer.dom(styled.root).removeChild(d);
    Polymer.dom.flush();
    assert.notInclude(d.getAttribute('style-scoped') || '', styled.is, 'scoping attribute not removed when removed from root');
    assert.notInclude(d.className, styled.is, 'scoping class not removed when removed from root');
    Polymer.dom(styled.root).appendChild(d);
    Polymer.dom.flush();
    assertComputed(d, '4px');
  });

  test('elements dynamically added/removed from host', function() {
    var d = document.createElement('div');
    d.classList.add('scoped');
    d.classList.add('blank');
    d.textContent = 'Dynamically... unScoped!';
    Polymer.dom(styled).appendChild(d);
    Polymer.dom.flush();
    assertComputed(d, '0px');
    Polymer.dom(document.body).appendChild(d);
    Polymer.dom.flush();
    assert.notInclude(d.getAttribute('style-scoped') || '', styled.is, 'scoping attribute not removed when added to other root');
    assert.notInclude(d.className, styled.is, 'scoping class not removed when added to other root');
    Polymer.dom(styled).appendChild(d);
    Polymer.dom.flush();
    assertComputed(d, '0px');
    Polymer.dom(styled).removeChild(d);
    Polymer.dom.flush();
    assert.notInclude(d.getAttribute('style-scoped') || '', styled.is, 'scoping attribute not removed when removed from root');
    assert.notInclude(d.className, styled.is, 'scoping class not removed when removed from root');
    Polymer.dom(styled).appendChild(d);
    Polymer.dom.flush();
    assertComputed(d, '0px');
  });

  test('keyframes change scope', function(done) {
    var xKeyframes = styled.$.keyframes;

    var onAnimationEnd = function() {
      xKeyframes.removeEventListener('animationend', onAnimationEnd);
      xKeyframes.removeEventListener('webkitAnimationEnd', onAnimationEnd);
      assertComputed(xKeyframes, '100px', 'left');

      xKeyframes = styled.$.keyframes2;

      onAnimationEnd = function() {
        xKeyframes.removeEventListener('animationend', onAnimationEnd);
        xKeyframes.removeEventListener('webkitAnimationEnd', onAnimationEnd);
        assertComputed(xKeyframes, '200px', 'left');
        done();
      };

      xKeyframes.addEventListener('animationend', onAnimationEnd);
      xKeyframes.addEventListener('webkitAnimationEnd', onAnimationEnd);

      Polymer.dom(xKeyframes).classList.add('special');
      xKeyframes.updateStyles();
      xKeyframes.animated = true;
    };

    xKeyframes.addEventListener('animationend', onAnimationEnd);
    xKeyframes.addEventListener('webkitAnimationEnd', onAnimationEnd);

    xKeyframes.animated = true;
    assertComputed(xKeyframes, '0px', 'left');
  });

  test('elements with computed classes', function() {
    assertComputed(styled.$.computed, '0px');
    styled.aClass = 'computed';
    assertComputed(styled.$.computed, '15px');
  });

  test('<a> with computed classes dynamically added', function() {
    assertComputed(styled.$.repeatContainer.firstElementChild, '0px');
    styled.aaClass = 'computeda';
    assertComputed(styled.$.repeatContainer.firstElementChild, '20px');
  });

  test('elements with hostAttributes: class', function() {
    assertComputed(styled.$.child, '16px');
  });

  test('type extension elements', function() {
    assertComputed(button, '10px');
    assertComputed(specialButton, '11px');
  });

  test('element subtree added via dom api', function() {
    var container = document.querySelector('x-dynamic-scope').$.container;
    var a = container.querySelector('.added');
    assertComputed(a, '17px');
    var b = container.querySelector('.sub-added');
    assertComputed(b, '18px');
  });

  test('styles in dynamically selected template', function() {
    var el = document.createElement('x-dynamic-template');
    document.body.appendChild(el);
    if (el.shadyRoot) {
      // style properly removed
      assert.notOk(el.querySelector('style'));
    }
    assertComputed(el, '40px');
    document.body.removeChild(el);
  });

  test('attribute inclusive selector and general sibling selectors', function() {
    var e = document.createElement('x-attr-selector');
    document.body.appendChild(e);
    assertComputed(e.$.bar1, '2px');
    assertComputed(e.$.bar2, '4px');
    assertComputed(e.$.bar3, '6px');
  });

  test('ShadowRoot-wide selectors', function() {
    var e = document.createElement('root-styles');
    document.body.appendChild(e);
    CustomElements.takeRecords();
    // :root
    assertComputed(e.$.child, 'rgb(123, 123, 123)', 'color');
    // :host > *
    assertComputed(e.$.child, '2px');
  });

    suite('scoped-styling-shady-only', function() {

      suiteSetup(function() {
        if (Polymer.Settings.useNativeShadow) {
          this.skip();
        }
      });

      test('element style precedence below document styles', function() {
        assertComputed(styledWide.$.priority, '1px');
      });

      test('styles shimmed in registration order', function() {
        var regList = Polymer.telemetry.registrations.map(function(reg) {
          return reg.is;
        });
        function regIndex(styleScope) {
          for (var i=0, r; (r=regList[i]); i++) {
            if (styleScope.match(new RegExp(r + '\\-?\\d*$'))) {
              return i;
            }
          }
        }
        var s$ = document.head.querySelectorAll('style[scope]');
        for (var i=0, scope, n=0, o=0; i<s$.length; i++) {
          scope = s$[i].getAttribute('scope');
          n = regIndex(scope);
          assert.isTrue(n >= o, 'style not in registration order: ' + scope);
          o = n;
        }
      });

      test('svg elements properly scoped', function() {
        assert.include(styled.$.circle.getAttribute('class'), 'x-styled');
        assert.include(styled.$.circle.getAttribute('class'), 'style-scope');
        assert.notInclude(styled.$.circle.getAttribute('class'), 'null');
        assertComputed(styled.$.circle, '1px', 'strokeWidth');
      });

      test('dom-bind content is unscoped', function() {
        var e = document.querySelector('#bind');
        e.dynamic = 'dynamic';
        var staticClassEl = document.querySelector('#dom-bind-static');
        assert.equal(staticClassEl.className, 'static');
        var dynamicClassEl = document.querySelector('#dom-bind-dynamic');
        assert.equal(dynamicClassEl.className, 'dynamic');
      });

      test('serializeValueToAttribute for class', function() {
        var s = styled.$.scopeClass;
        var scope = s.$.scope;
        assert.isTrue(s.classList.contains('style-scope'));
        assert.isTrue(s.classList.contains('x-styled'));
        s.serializeValueToAttribute('foo', 'class');
        assert.isTrue(s.classList.contains('foo'));
        assert.isTrue(s.classList.contains('style-scope'));
        assert.isTrue(s.classList.contains('x-styled'));
        //
        assert.isTrue(scope.classList.contains('style-scope'));
        assert.isTrue(scope.classList.contains('x-scope-class'));
        s.serializeValueToAttribute('foo', 'class', scope);
        assert.isTrue(scope.classList.contains('foo'));
        assert.isTrue(scope.classList.contains('style-scope'));
        assert.isTrue(scope.classList.contains('x-scope-class'));
      });

      test('mixed-case elements', function() {
        var x = document.createElement('x-mixed-case');
        document.body.appendChild(x);
        assertComputed(x, '13px');
        x = document.createElement('button', 'x-mixed-case-button');
        document.body.appendChild(x);
        assertComputed(x, '14px');
      });

      test('specificity of :host selector with class', function() {
        assertComputed(document.querySelector('x-specificity'), '1px');
        assertComputed(document.querySelector('x-specificity.bar'), '2px');
      });

      test('specificity of ::content > :not(template) selector', function() {
        assertComputed(document.querySelector('[is=x-specificity-nested]'), '10px');
      });

      test('overwriting mixin properties', function() {
        var root = document.querySelector('x-overriding');
        assertComputed(root.querySelector('.red'), '1px');
        assertComputed(root.querySelector('.green'), '2px');
        assertComputed(root.querySelector('.red-2'), '1px');
        assertComputed(root.querySelector('.blue'), '3px');
      });

      test('x-scope with no class attribute', function () {
        var e = document.querySelector('x-scope-no-class > div');
        assert.isTrue(e.classList.contains('style-scope'));
        assert.isTrue(e.classList.contains('x-scope-no-class'));
      });
    });

    test('svg classes are dynamically scoped correctly', function() {
      var container = document.querySelector('x-dynamic-svg').$.container;
      var svg = container.querySelector('.svg');
      var computed = getComputedStyle(svg);
      assert.equal(computed.height, '24px');
      assert.equal(computed.width, '24px');
      var circle = container.querySelector('#circle');
      computed = getComputedStyle(circle);
      assert.equal(computed['fill-opacity'], '0.5');
    });

    test(':host with element tag selector', function() {
      var s1 = document.createElement('x-shared1');
      document.body.appendChild(s1);
      assertComputed(s1, '2px');
      var s2 = document.createElement('x-shared2');
      document.body.appendChild(s2);
      assertComputed(s2, '4px');
    });

    test(':host with superset of element tag selector does not leak', function() {
      var t = document.createElement('div');
      t.textContent = 'host leak test';
      t.classList.add('x-shared1');
      document.body.appendChild(t);
      assertComputed(t, 'auto', 'top');
    });

    test(':host(...) with non-matching type selector does not leak', function() {
      var t = document.createElement('x-shared1x-shared2');
      t.textContent = ':host(non-matching-type-selector)';
      document.body.appendChild(t);
      assertComputed(t, '0px');
      t = document.createElement('x-shared2x-shared1');
      t.textContent = ':host(non-matching-type-selector)';
      document.body.appendChild(t);
      assertComputed(t, '0px');
    });

});

</script>
</body>
</html>
